/**
 * @author Darron Schall
 * @version 1.0
 * 
 * Aug. 24, 2003
 * 
 * PasswordGenerator for creating random passwords.  
 * 
 * Four password flags are available to dictate generation,
 * or a template can be specificed to generate a string with 
 * a specified character type at each position.
 * 
 * Revision History:
 * Rev Date			Who		Description
 * 1.0 8/24/03		darron	Initial Draft
 * --------------------------------------
 * License For Use
 * --------------------------------------
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name of the author may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

using System;

namespace KangaUtils
{
	/// <summary>
	/// Summary description for PasswordGenerator.
	/// </summary>
	public sealed class PasswordGenerator
	{
		public const int MIN_PASSWORD_LENGTH = 3;
		
		private int length;
	
		private bool lowercaseIncluded;
		private bool numbersIncluded;
		private bool othersIncluded;
		private bool uppercaseIncluded;

		private string password;
		private string template;

		// The C# random class is a little easier to use
		// than Java's Math.random, as you'll see in the 
		// random methods later on...
		private static Random random = new Random();
		
		public PasswordGenerator()
		{
			// by default, invlude lowercase, uppercase, and numbers
			// in the password, and make it 8 characters long.
			password = "";
			template = "";
			length = 8;
			lowercaseIncluded = true;
			uppercaseIncluded = true;
			numbersIncluded = true;
			othersIncluded = false;
		
			// start the ball rolling by generating a password so that
			// we keep our data integrity 
			// i.e. so length matches password.length());
			generatePassword();
		}

		/**
		* @return true if at least one of the password generation flags
		* is true, otherwise returns false
		*/
		private bool flagsOK() 
		{
			return lowercaseIncluded
				|| uppercaseIncluded
				|| numbersIncluded
				|| othersIncluded;
		}
		
		/**
		 * @return a random lowercase character from 'a' to 'z'
		 */
		private static char randomLowercase() 
		{
			return (char) random.Next('a', 'z'+1);
		}

		/**
		 * @return a random uppercase character from 'A' to 'Z'
		 */
		private static char randomUppercase() 
		{
			return (char) random.Next('A', 'Z'+1);
		}

		/**
		 * @return a random character in this list: !"#$%&'()*+,-./
		 */
		private static char randomOther() 
		{
			return (char) random.Next('!', '/'+1);
		}

	
		/**
		 * @return a random character from '0' to '9'
		 */
		private static char randomNumber() 
		{
			return (char) random.Next('0', '9'+1);
		}

		// C# lets us use "delegates" to create a variable
		// that stores a reference to a function...
		delegate char RandomCharacter();
		
		public void generatePassword() 
		{
			// clear password if necessary
			if (password.Length != 0) 
			{
				password = "";
			}

			// a template being defined overrides all flags
			if (template.Length > 0) 
			{
				// assign the password length the same
				// value as the template length
				length = template.Length;
				for (int i = 0; i < length; i++) 
				{
					switch (template[i]) 
					{
						case 'a' :
							password += randomLowercase();
							break;

						case 'A' :
							password += randomUppercase();
							break;

						case 'n' :
						case 'N' :
							password += randomNumber();
							break;

						case 'o' :
						case 'O' :
							password += randomOther();
							break;
						
						// there is no default case here because
						// our setter/constructor ensures data
						// integrity
					}
				}
			} 
			else // create the password based on the flags
			{
				// create an array of function refernces, with space for up to 4
				// functions
				RandomCharacter[] r = new RandomCharacter[4];
				
				// keep track of how many array locations we're actually using
				int count = 0; 

				if (lowercaseIncluded) 
				{
					// using our delegate, store a reference to the randomLowercase
					// function in our array
					r[count++] = new RandomCharacter(PasswordGenerator.randomLowercase);
				}
				if (uppercaseIncluded) 
				{
					r[count++] = new RandomCharacter(PasswordGenerator.randomUppercase);
				}
				if (othersIncluded) 
				{
					r[count++] = new RandomCharacter(PasswordGenerator.randomOther);
				}
				if (numbersIncluded) 
				{
					r[count++] = new RandomCharacter(PasswordGenerator.randomNumber);
				}
				
				for (int i = 0; i < length; i++) 
				{
					// execute our function reference
					// .. with C# delegates, this code is a little cleaner
					// and easier to read.
					password += r[(int) random.Next(0,count)]();
				}
			}
		} // end generatePassword method
		
		public string Password 
		{
			get 
			{
				return password;
			}
		}

		public string Template 
		{
			get 
			{
				return template;
			}
			set 
			{
				template = "";
				bool throwError = false;
				int errorPosition = 0;

				// make sure the template contains only legal characters
				for (int i = 0; i < value.Length; i++) 
				{
					switch (value[i]) 
					{
						case 'a' :
						case 'A' :
						case 'n' :
						case 'N' :
						case 'o' :
						case 'O' :
							template += value[i];
							break;
					
						default :
							if (!throwError) 
							{
								throwError = true;
								// keep track of where the error occured
								errorPosition = i;
							}
							break;
					}
				}
				
				if (throwError) 
				{
					throw new FormatException("Password template contains an invalid character at position " + errorPosition + " - " + value[errorPosition]);
				}
			}
		}

		public int Length
		{
			get 
			{
				return length;
			}
			set 
			{
				// make sure the length is at least the MIN_PASSWORD_LENGTH
                length = (value < MIN_PASSWORD_LENGTH) ? MIN_PASSWORD_LENGTH : value;
			}
		}

		public bool LowercaseIncluded
		{
			get 
			{
				return lowercaseIncluded;
            }
			set 
			{
				lowercaseIncluded = value;

				// did we turn off the last flag?  if so
				// turn it back on and report error
				if (value == false && !flagsOK()) 
				{
					lowercaseIncluded = true;
					throw new ArgumentException("At least one flag must be on to generate a password");
				}
			}
		}

		public bool NumbersIncluded 
		{
			get 
			{
				return numbersIncluded;
			}
			set 
			{
				numbersIncluded = value;

				// did we turn off the last flag?  if so
				// turn it back on and report error
				if (value == false && !flagsOK()) 
				{
					numbersIncluded = true;
					throw new ArgumentException("At least one flag must be on to generate a password");
				}
			}
		}

		public bool OthersIncluded 
		{
			get 
			{
				return othersIncluded;
			}
			set 
			{
				othersIncluded = value;

				// did we turn off the last flag?  if so
				// turn it back on and report error
				if (value == false && !flagsOK()) 
				{
					othersIncluded = true;
					throw new ArgumentException("At least one flag must be on to generate a password");
				}
			}
		}

		public bool UppercaseIncluded
		{
			get 
			{
				return uppercaseIncluded;
			}
			set 
			{
				uppercaseIncluded = value;

				// did we turn off the last flag?  if so
				// turn it back on and report error
				if (value == false && !flagsOK()) 
				{
					uppercaseIncluded = true;
					throw new ArgumentException("At least one flag must be on to generate a password");
				}
			}
		}
	}
}
